<?php


namespace lib;

use app\api\model\File;
use app\api\model\FileType;
use app\common\controller\Util;
use think\Exception;

/**
 * 文件上传类
 * Class Upload
 * @package lib
 */
class Upload
{
    /**
     * 分片上传是否需要合并
     * @var bool
     */
    protected $mergin = false;
    protected $config = [];
    /**
     * @var File
     */
    protected $file = null;
    protected $fileInfo = null;
    protected $tid = null;

    public function __construct($file = null, $tid = null)
    {
        $this->config = config('upload');
        if ($file) {
            $this->setFile($file);
        }
        if ($tid) {
            $this->setTid($tid);
        }
    }

    /**
     * 获取文件
     * @return File|null
     */
    public function getFile(): File
    {
        return $this->file;
    }

    /**
     * 设置类别id
     * @param int $tid
     * @throws Exception
     */
    public function setTid($tid = 0)
    {
        $tid = $tid == -1 ? 0 : $tid;
        // 校验分类id不存在
        $checkTidExist = FileType::get(['id' => $tid]);
        if (empty($checkTidExist) && $tid != 0) {
            throw new Exception('附件分类不存在!');
        }
        $this->tid = $tid;
    }

    /**
     * 设置文件
     * @param $file
     * @throws Exception
     */
    public function setFile($file)
    {
        // 判断文件是否为空
        if (empty($file)) {
            throw new Exception('未选择文件或文件超出服务器限制!');
        }
        $fileInfo = $file->getInfo();
        $suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
        $suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
        $fileInfo['suffix'] = $suffix;
        $this->file = $file;
        $this->fileInfo = $fileInfo;
        $this->checkDisabledFile();
        $this->checkSize();
    }

    /**
     * 检测上传文件是否为php和html文件
     * @return bool
     * @throws Exception
     */
    public function checkDisabledFile(): bool
    {
        // 判断上传文件是否属于禁止上传
        if (in_array($this->fileInfo['type'], [$this->config['disabledFileType']])
            || in_array($this->fileInfo['suffix'], [$this->config['disabledSuffix']])
            || preg_match("/^php(.*)/i", $this->fileInfo['suffix'])) {
            throw new Exception('禁止上传该类型文件!');
        }
        return true;
    }

    /**
     * 检测文件类型
     * @return bool
     * @throws Exception
     */
    public function checkMimeType(): bool
    {
        // 获取可上传的文件类型
        $mimeTypeArr = explode(',', strtolower($this->config['mimeType']));
        // 获取上传的文件类型
        $typeArr = explode('/', $this->fileInfo['type']);
        // 判断mimetype值是否正确
        if (stripos($this->fileInfo['type'], '/') === false) {
            throw new Exception('上传的文件格式不正确!');
        }
        // 校验文件后缀是否合法
        if ($this->config['mimeType'] === '*'
            || in_array($this->fileInfo['suffix'], $mimeTypeArr)
            || in_array('.' . $this->fileInfo['suffix'], $mimeTypeArr)
            || in_array($typeArr[0] . "/*", $mimeTypeArr)
            || in_array($this->fileInfo['type'], $mimeTypeArr)
            && stripos($this->fileInfo['type'], '/' !== false)
        ) {
            return true;
        }
        throw new Exception('上传的文件格式不正确!');
    }

    /**
     * 检测文件大小是否超出限制
     * @throws Exception
     */
    public function checkSize()
    {
        // 校验文件大小配置
        preg_match('/([0-9\.]+)(\w+)/', $this->config['maxsize'], $matches);
        // 获取文件大小
        $size = $matches ? $matches[1] : $this->config['maxsize'];
        // 获取大小单位
        $type = $matches ? strtolower($matches[2]) : 'b';
        // 大小单位字典
        $typeDict = [
            'b' => 0,
            'k' => 1,
            'kb' => 1,
            'm' => 2,
            'mb' => 2,
            'gb' => 3,
            'g' => 3
        ];
        // 计算大小
        $size = (int)($size * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0));
        if ($this->fileInfo['size'] > $size) {
            throw new Exception('上传文件大小超出服务器限制!');
        }
    }

    /**
     * 获取文件后缀
     * @return string
     */
    public function getSuffix(): string
    {
        return $this->fileInfo['suffix'] ?: 'file';
    }

    /**
     * 获取存储的文件名
     * @param null $savekey
     * @param null $filename
     * @param null $md5
     * @return mixed|string|string[]
     */
    public function getSavekey($savekey = null, $filename = null, $md5 = null)
    {
        if ($filename) {
            $suffix = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
            $suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : '';
        } else {
            $suffix = $this->fileInfo['suffix'];
        }
        $filename = $filename ? $filename : ($suffix ? substr($this->fileInfo['name'], 0, strripos($this->fileInfo['name'], '.')) : $this->fileInfo['name']);
        $md5 = $md5 ? $md5 : md5_file($this->fileInfo['tmp_name']);
        $replaceArr = [
            '{year}' => date('Y'),
            '{mon}' => date('m'),
            '{day}' => date('d'),
            '{hour}' => date('H'),
            '{min}' => date('i'),
            '{sec}' => date('s'),
            '{random}' => Util::random(16),
            '{random32}' => Util::random(32),
            '{filename}' => substr($filename, 0, 100),
            '{suffix}' => $suffix,
            '{.suffix}' => $suffix ? '.' . $suffix : '',
            '{filemd5}' => $md5,
        ];
        $savekey = $savekey ? $savekey : $this->config['saveKey'];
        $savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey);
        return $savekey;
    }

    /**
     * 普通文件上传
     * @param null $savekey
     * @param int $tid
     * @return File
     * @throws Exception
     * @throws \think\exception\DbException
     */
    public function upload($savekey = null): File
    {
        // 判断文件是否为空
        if (empty($this->file)) {
            throw new Exception('未选择文件或文件超出服务器限制');
        }

        $this->checkSize();
        $this->checkDisabledFile();
        $this->checkMimeType();

        $savekey = $savekey ? $savekey : $this->getSavekey();
        $savekey = '/' . ltrim($savekey, '/');
        // 设置上传文件路径
        $uploadDir = substr($savekey, 0, strripos($savekey, '/') + 1);
        $fileName = substr($savekey, strripos($savekey, '/') + 1);
        $destDir = ROOT_PATH . 'public' . str_replace('/', DS, $uploadDir);
        $sha1 = $this->file->hash();
        if ($this->mergin) {

        } else {
            $file = $this->file->move($destDir, $fileName);
            if (!$file) {
                // 捕获上传失败的错误信息
                throw new Exception($this->file->getError());
            }
        }
        $this->file = $file;
        $params = array(
            'filename' => mb_substr(htmlspecialchars(strip_tags($this->fileInfo['name'])), 0, 100),
            'tid' => $this->tid,
            'filesize' => $this->fileInfo['size'],
            'mimetype' => $this->fileInfo['type'],
            'uri' => $uploadDir . $file->getSaveName(),
            'upload_time' => time(),
            'sha1' => $sha1
        );
        $files = new File();
        $files->data(array_filter($params));
        $files->save();
        return $files;
    }
}